{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../common/rfsoc_book_banner.jpg\" alt=\"University of Strathclyde\" align=\"left\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block\" style=\"background-color: #c7b8d6; padding: 10px\">\n",
    "    <p style=\"color: #222222\">\n",
    "        <b>Note:</b>\n",
    "        <br>\n",
    "        This Jupyter notebook uses hardware features of the Zynq UltraScale+ RFSoC device. Therefore, the notebook cells will only execute successfully on an RFSoC platform.\n",
    "        <br>\n",
    "        <b>This Jupyter notebook is not compatible with the ZCU216 development board as it does not contain the SD-FEC integrated block.</b>\n",
    "    </p>\n",
    "</div>\n",
    "\n",
    "# Notebook Set H\n",
    "\n",
    "---\n",
    "\n",
    "## 02 - FEC Encoding\n",
    "This is the second notebook in the series exploring Soft Decision Forward Error Correction (SD-FEC) on RFSoC. Previously, we have learned about parity check matrices and LDPC codes. In this notebook, we will make use of an SD-FEC integrate block on the RFSoC to encode data that has been generated using Python.\n",
    "\n",
    "## Table of Contents\n",
    "* [1. Introduction](#nb2_introduction)\n",
    "    * [1.1. Design Overview](#nb2_design_overview)\n",
    "    * [1.2. Notebook Setup](#nb2_notebook_setup)\n",
    "* [2. Configure Encoder](#configure_encoder)\n",
    "    * [2.1. Add LDPC Parameters](#nb2_add_ldpc_params)\n",
    "* [3. Setup Buffers](#nb2_setup_buffers)\n",
    "    * [3.1. Control](#nb2_control)\n",
    "    * [3.2. Status](#nb2_status)\n",
    "    * [3.3. Transmit and Receive](#nb2_transmit_and_receive)\n",
    "* [4. Encode Data](#encode_data)\n",
    "* [5. Conclusion](#nb2_conclusion)\n",
    "\n",
    "## References\n",
    "* [1] - [AMD-Xilinx, \"Soft-Decision FEC Integrated Block v1.1: LogiCORE IP Product Guide\", October 2022](https://docs.xilinx.com/r/en-US/pg256-sdfec-integrated-block)\n",
    "\n",
    "## Revision\n",
    "* **v1.0** | 16/01/23 | *First Revision*\n",
    "* **v1.1** | 19/05/23 | *Minor changes for new development boards (ZCU208 & ZCU216)*\n",
    "\n",
    "---\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Introduction  <a class=\"anchor\" id=\"nb2_introduction\"></a>\n",
    "This notebook introduces the integrated SD-FEC block, configured as an encoder in Non-5G New Radio (NR) mode. The run-time configuration of the block is explained for LDPC encoding using the supported Data-Over-Cable Service Interface Specifications (DOCSIS) standard. Transfer of data to and from the SD-FEC core is demonstrated using DMAs. Additionally, the control and status interfaces found on the core are detailed, and the construction and interpretation of the words associated with these interfaces is explored. \n",
    "\n",
    "### 1.1. Design Overview <a class=\"anchor\" id=\"nb2_design_overview\"></a>\n",
    "The programmable logic design used in this notebook is illustrated in Figure 1.\n",
    "<a class=\"anchor\" id=\"fig-1\"></a>\n",
    "<center><figure>\n",
    "<img src='./images/sdfec_encoder_block_design.svg' width='1000'/>\n",
    "    <figcaption><b>Figure 1: Functional block diagram illustrating the loop-back implementation of the SD-FEC encoder.</b></figcaption>\n",
    "</figure></center>\n",
    "\n",
    "An SD-FEC block is configured as an LDPC encoder and is in a loop-back architecture with the processing system. Four buffers are required for the movement of data between our Jupyter environment and the SD-FEC core. Two DMAs (ctrl and data) are employed to move data between the Processing System (PS) and Programmable Logic (PL). The *ctrl* DMA is responsible for the control and status buffers and the *data* DMA is responsible for the transmit and receive buffers. The transmit buffer will contain randomly generated data that form our information bits. This buffer will be sent to the SD-FEC core where the data will be encoded and additional parity bits will be added, forming an encoded block of data. The encoded block will be received in the receive buffer which we can then compare to the transmit buffer in Jupyter.\n",
    "\n",
    "### 1.2. Notebook Setup <a class=\"anchor\" id=\"nb2_notebook_setup\"></a>\n",
    "We must first setup the notebook by importing the required libraries and downloading the bitstream to the board. The Python package *xsdfec* ships with PYNQ and is used for interacting with instances of the SD-FEC integrated block that are found in the programmable logic design."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import JSON\n",
    "from pynq import allocate\n",
    "import numpy as np\n",
    "import xsdfec\n",
    "import strath_sdfec.helper_functions as hf\n",
    "\n",
    "from strath_sdfec.overlay import SdfecOverlay\n",
    "ol = SdfecOverlay()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Configure Encoder <a class=\"anchor\" id=\"configure_encoder\"></a>\n",
    "The SD-FEC core has been part-configured in Vivado when creating the programmable logic design. The core has been configured as an encoder, in Non-5G NR mode and the supported DOCSIS 3.1 LDPC standard has been specified. This standard provides 5 LDPC codes. The parameters for these codes are stored in the Hardware Hand-off file (\\*.hwh) associated with the bitstream. When the *xsdfec* Python driver binds to an SD-FEC instance in the programmable logic design, the \\*.hwh file is parsed and the relevant code parameters are converted into a Python dictionary. We can review the names of the available LDPC codes for a given SD-FEC instance using the *available_ldpc_params()* function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fec = ol.ldpc_encoder.sd_fec\n",
    "ldpc_params = fec.available_ldpc_params()\n",
    "ldpc_params"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To inspect the parameters of a given code, we can index the LDPC code parameters dictionary using one of the code names obtained in the cell above. This dictionary gives us information such as whether the SD-FEC block is configured as an encoder (*enc_OK*=1) or decoder (*dec_OK*=1). Information about the code's parity check matrix is also provided: *n* — the encoded block length in bits; *k* — the number of information bits in a block; *p* — the sub-matrix size. These parameters will be useful when determining the dimensions of our data buffers. Other notable entries include: *sc_table* — scale table; *la_table* - layer table; and *qc_table* — quasi-cyclic table."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "JSON(fec._code_params.ldpc['docsis_short_encode'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1. Add LDPC Parameters <a class=\"anchor\" id=\"nb2_add_ldpc_params\"></a>\n",
    "To use an LDPC code, it must be added into the SD-FEC's internal memory. The general procedure for adding an LDPC code is outlined in the cell below. First, disable the SD-FEC core and then use the *add_ldpc_params()* function to add code parameters for an LDPC code before enabling the SD-FEC core again. The *add_ldpc_params()* function takes five arguments: \n",
    "* Code ID\n",
    "* Scale (SC) Table Offset\n",
    "* Layer (LA) Table Offset\n",
    "* Quasi-Cyclic (QC) Table Offset\n",
    "* Code Name\n",
    "\n",
    "As this is the first code being placed in the SD-FEC's internal memory, the offset for each of the tables is zero."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fec.CORE_AXIS_ENABLE = 0                  # Ensure FEC is disabled (000000)\n",
    "\n",
    "code_id = 0\n",
    "sc_offset = 0\n",
    "la_offset = 0\n",
    "qc_offset = 0\n",
    "code_name = 'docsis_short_encode'\n",
    "\n",
    "fec.add_ldpc_params(code_id,sc_offset,la_offset,qc_offset,code_name)\n",
    "\n",
    "fec.CORE_AXIS_ENABLE = 63                  # Enable FEC (111111)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Each SD-FEC core can store up to 128 codes. In practice however, a fewer number of LDPC codes can be loaded simultaneously. This is because the tables themselves have limits to their sizes. The code in the cell below uses *share_table_size()* to obtain the amount of space required for the DOCSIS Short code. These sizes are compared to the maximum sizes of each of the tables."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "table_sizes = fec.share_table_size(code_name)\n",
    "print(code_name, table_sizes)\n",
    "print('SC Table Size:',len(fec.LDPC_SC_TABLE))\n",
    "print('LA Table Size:',len(fec.LDPC_LA_TABLE))\n",
    "print('QC Table Size:',len(fec.LDPC_QC_TABLE))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A helper function *add_all_ldpc_params()* is provided alongside this series of notebooks. It makes use of the three xsdfec functions that have been introduced thus far: *available_ldpc_params()*, *add_ldpc_params()* and *share_table_size()*. Each of the available LDPC codes is looped over and added to the FEC instance, making sure to provide the correct table offsets so that no parameters are overlapping."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hf.add_all_ldpc_params(fec)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have now configured our encoder by adding all the available codes to its internal memory. This allows for codes to easily be switched between on a block-by-block basis.\n",
    "\n",
    "## 3. Setup Buffers <a class=\"anchor\" id=\"nb2_setup_buffers\"></a>\n",
    "We require four buffers when the encoder is in loop-back with the processing system. A transmit buffer will be utilised to send information bits to the encoder and a receive buffer will move the encoded block back into the PS for inspection. The other two buffers are used for control and status words which must be sent to and received from the encoder for every data block that is to be encoded. Figure 2 below illustrates the movement of data to and from the SD-FEC core when configured as an encoder.\n",
    "\n",
    "<a class=\"anchor\" id=\"fig-2\"></a>\n",
    "<center><figure>\n",
    "<img src='./images/ctrl_status_fec.svg' width='800'/>\n",
    "    <figcaption><b>Figure 2: SD-FEC core interfaces when encoding data.</b></figcaption>\n",
    "</figure></center>\n",
    "\n",
    "A control word should ideally precede a data block being input on the *DIN* interface. This improves the throughput of the system as the SD-FEC core cannot process data on its *DIN* interface until it has read in and processed the control word on the *CTRL* interface. The status word returned by the SD-FEC core should also be read, as not doing so will cause the clock-domain-crossing (CDC) FIFO on the *STATUS* interface to fill which will result in the SD-FEC core stalling. There are CDC FIFOs on all the interfaces mentioned here, meaning there is some robustness in data transfer. However, these are small and their primary purpose is to facilitate clock domain crossings.\n",
    "\n",
    "### 3.1. Control <a class=\"anchor\" id=\"nb2_control\"></a>\n",
    "Let us look at the control buffer in more detail. In this setup, the buffer is only required to hold one 32-bit word which will be sent to the SD-FEC core using AXI4-Stream for every data block to be encoded. Table 1 details the control interface LDPC encoding when the SD-FEC core is configured in Non-5G NR mode. \n",
    "\n",
    "<a class=\"anchor\" id=\"tab-1\"></a>\n",
    "<center><figure>\n",
    "    <figcaption><b>Table 1: Control interface for LDPC encoding in Non-5G NR mode.</b></figcaption>\n",
    "    <br>\n",
    "    <table style=\"width:1000\">\n",
    "      <tr>\n",
    "        <th>Field</th>\n",
    "        <th>Bits</th>\n",
    "        <th>Type</th>\n",
    "        <th>Range</th>\n",
    "        <th>Description</th>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>External Block ID</td>\n",
    "        <td>31:24</td>\n",
    "        <td>uint8</td>\n",
    "        <td>0 to 255</td>\n",
    "        <td>External block identifier to be passed through to status output</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>-</td>\n",
    "        <td>23:7</td>\n",
    "        <td>-</td>\n",
    "        <td>-</td>\n",
    "        <td>Reserved</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Code ID</td>\n",
    "        <td>6:0</td>\n",
    "        <td>uint7</td>\n",
    "        <td>0 to 127</td>\n",
    "        <td> Code number used to specify which set of LDPC code parameters are to be used on the block</td>\n",
    "      </tr>\n",
    "    </table>\n",
    "</figure></center>\n",
    "\n",
    "The two relevant fields, when the SD-FEC core is configured as an encoder, are: *External Block ID* and *Code ID*. The *External Block ID* can be any number between 0 and 255 and is simply passed through to the status register for debug purposes, allowing for the encoded block to be identified. The *Code ID* parameter is the number associated with the code which is to be used to perform the encoding. This number was established when we wrote all the available LDPC code parameters to the SD-FEC core in the previous code cell and can be found under the 'Code ID' column of the output.\n",
    "\n",
    "Each of our five LDPC codes has a code ID ranging from 0 to 4. As we iterated through the dictionary in order, we can obtain the code ID for a given code easily by obtaining the index for a given code name."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "code_name = 'docsis_medium_encode'\n",
    "code_id = ldpc_params.index(code_name)\n",
    "print('Code ID:',code_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To construct the code word, we simply populate each of the fields in the 32-bit binary number with the values we want. There are different ways of achieving this. The method outlined below uses bit shifts to move the values to the correct bit position in the control word. As the least significant bit of Block ID is the 24th bit of the control word, it should be shifted 24 times to the left. Similarly, because the Code ID exists as the least significant bits of the control word, it does not need shifted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "block_id = 127\n",
    "\n",
    "ctrl_word = (block_id << 24) + (code_id << 0)\n",
    "print('Control Word (bits):','{0:032b}'.format(ctrl_word))\n",
    "print('Control Word (int):',ctrl_word)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A helper function is provided which makes it easier to construct the control words. It accepts a dictionary as an input with the fields that are to be populated. If a field is not to be used or to remain as the default of zero, then these fields can be omitted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ctrl_params = {'block_id' : 127, \n",
    "               'code_id' : code_id}\n",
    "ctrl_word = hf.create_ctrl_word(ctrl_params,readout='encoder')\n",
    "print('Control Word (int):',ctrl_word)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Having constructed our control word, we can create a buffer and populate it. The buffer is only required to hold one sample — our control word."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ctrl_buffer = allocate(shape=(1,), dtype=np.uint32)\n",
    "ctrl_buffer[0] = ctrl_word"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2. Status <a class=\"anchor\" id=\"nb2_status\"></a>\n",
    "\n",
    "As with the control buffer, the status buffer will contain a 32-bit word. Table 2 details the fields contained in the word.\n",
    "\n",
    "<a class=\"anchor\" id=\"tab-2\"></a>\n",
    "<center><figure>\n",
    "    <figcaption><b>Table 2: Status Interface for LDPC encoding in Non-5G NR mode.</b></figcaption>\n",
    "    <br>\n",
    "    <table style=\"width:1000\">\n",
    "      <tr>\n",
    "        <th>Field</th>\n",
    "        <th>Bits</th>\n",
    "        <th>Type</th>\n",
    "        <th>Range</th>\n",
    "        <th>Description</th>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>External Block ID</td>\n",
    "        <td>31:24</td>\n",
    "        <td>uint8</td>\n",
    "        <td>0 to 255</td>\n",
    "        <td>External block identifier supplied through control input</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>-</td>\n",
    "        <td>23:15</td>\n",
    "        <td>-</td>\n",
    "        <td>-</td>\n",
    "        <td>Reserved</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Hard/Soft Output</td>\n",
    "        <td>14:14</td>\n",
    "        <td>bit1</td>\n",
    "        <td>1</td>\n",
    "        <td>Hard output (fixed value)</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Encode/Decode Operation</td>\n",
    "        <td>13:13</td>\n",
    "        <td>bit1</td>\n",
    "        <td>1</td>\n",
    "        <td>Encode operation (fixed value)</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>-</td>\n",
    "        <td>12:7</td>\n",
    "        <td>-</td>\n",
    "        <td>-</td>\n",
    "        <td>Reserved</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Code ID</td>\n",
    "        <td>6:0</td>\n",
    "        <td>uint7</td>\n",
    "        <td>0 to 127</td>\n",
    "        <td>Code number specifying the LDPC code parameters used to decode the block</td>\n",
    "      </tr>\n",
    "    </table>\n",
    "</figure></center>\n",
    "\n",
    "We have already encountered the *External Block ID* and *Code ID* fields. The two new fields are *Hard/Soft Output* and *Encode/Decode Operation*. *Hard/Soft Output* informs us of the form the data is being output in. Data can either be hard or soft. Hard data is where the bit values have been determined to either be a 0 or a 1 using some form of decision making. Soft data uses more bits to describe the value of a single bit, where the certainty of a bit being a 0 or a 1 can also be conveyed. This only applies when decoding data and as such, the value returned from an SD-FEC core configured as an encoder will always be 1, indicating that the data being output is hard data. The *Encode/Decode Operation* field informs us of the operation that has been performed on the data being output — whether the data has been encoded or decoded. Here, a 1 indicates an encoding operating and a 0, decoding.\n",
    "\n",
    "To obtain the values of individual fields from the status word returned, bit masking can be used to select areas of the word to be retained. These bits can then be shifted to the right to obtain the value. Another method is to convert the base 10 word into a base 2 binary character array and index the desired bits before converting back into base 10. This second method is the one employed in the helper function *print_status_reg()* which will be used later in this notebook. \n",
    "\n",
    "Presently, we only need to create an empty buffer for holding the status word returned by the SD-FEC core. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "status_buffer = allocate(shape=(1,), dtype=np.uint32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3. Transmit and Receive <a class=\"anchor\" id=\"nb2_transmit_and_receive\"></a>\n",
    "Here we will create our two data buffers, transmit and receive. The transmit data buffer will hold our information bits and will be input to the SD-FEC core for encoding. The receive data buffer will hold our encoded data, which comprises our information bits in addition to parity bits. \n",
    "\n",
    "Let us inspect some code parameters for our chosen LDPC code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n = fec._code_params.ldpc[code_name]['n']\n",
    "k = fec._code_params.ldpc[code_name]['k']\n",
    "p = fec._code_params.ldpc[code_name]['p']\n",
    "\n",
    "print(\"\"\"[n] Block Length (bits): %s\\n[k] Information Bits: %s\"\"\"% (n, k))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The parameter *k* informs us of how many information bits are in a given block. The parameter *n* indicates how many bits total are in an encoded block. Therefore, from these two parameters, we know that our transmit buffer should contain *k* bits and our receive buffer *n* bits. However, we also need to know the architecture of the programmable logic design when creating these buffers as we will not be transferring individual bits. From Figure 1 we can see that the design transfers the data with wordlengths of 8 bits or a byte. The lengths of our data buffers must therefore be *k*/8 and *n*/8. \n",
    "\n",
    "In the code cell below, we create our two data buffers of the correct size and populate the transmit buffer with random integers between 0 and 255 as this is the range of the 8-bit wordlength used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "K = int(np.ceil(k/8))  # k in bytes\n",
    "N = int(np.ceil(n/8))  # n in bytes\n",
    "\n",
    "tx_enc_buf = allocate(shape=(K,), dtype=np.uint8)\n",
    "rx_enc_buf = allocate(shape=(N,), dtype=np.uint8)\n",
    "\n",
    "# Populate transmit buffer with random data\n",
    "for i in range(len(tx_enc_buf)):\n",
    "    tx_enc_buf[i] = np.random.randint(0,256)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Depending on both the code used and wordlength employed however, the buffers may not be exactly the correct size. The cell below iterates through all of the available codes and prints out the parameters *k* and *n* alongside these parameters divided by the AXI4-Stream wordlength. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"{:^26} {:^8} {:^8} {:^8} {:^8}\".format('Code Name','k','n','k/8','n/8'))\n",
    "for cn in fec.available_ldpc_params():\n",
    "    n = fec._code_params.ldpc[cn]['n']\n",
    "    k = fec._code_params.ldpc[cn]['k']\n",
    "    print(\"{:^26} {:^8} {:^8} {:^8} {:^8}\".format(code_name,k,n,k/8,n/8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It can be seen that for almost all cases, the wordlength divides exactly into *k* or *n*. The DOCSIS Medium code's encoded length, *n*, when divided by the wordlength of 8 results in 742.5. This means that the receive buffer when using this code will return 742 valid 8-bit words, however the 743rd word will only contain 4 bits of valid data on the least significant bits meaning the most significant 4 bits of this word should be discarded. \n",
    "\n",
    "## 4. Encode Data <a class=\"anchor\" id=\"encode_data\"></a>\n",
    "Having configured the SD-FEC core and created all of the required buffers, we can now perform LDPC encoding. Running the cell below will send our control word and transmit buffer to the SD-FEC block where the information bits will be encoded. The encoded data output will be returned in our receive buffer along with a status word in the status buffer. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Assign data and ctrl DMAs to shorter variables\n",
    "dma_data = ol.ldpc_encoder.axi_dma_data\n",
    "dma_ctrl = ol.ldpc_encoder.axi_dma_ctrl\n",
    "\n",
    "# Initiate transfers\n",
    "dma_ctrl.recvchannel.transfer(status_buffer)\n",
    "dma_data.recvchannel.transfer(rx_enc_buf)\n",
    "dma_ctrl.sendchannel.transfer(ctrl_buffer)\n",
    "dma_data.sendchannel.transfer(tx_enc_buf)\n",
    "\n",
    "# Wait for transfers to complete\n",
    "dma_ctrl.sendchannel.wait()\n",
    "dma_data.sendchannel.wait()\n",
    "dma_data.recvchannel.wait()\n",
    "dma_ctrl.recvchannel.wait()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using a helper function, we can inspect the received status word. It should closely align to the control word we created. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hf.print_status_reg(status_buffer, 'encoder')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can inspect both our data buffers. The cell below serialises our buffers, meaning the data is displayed as single bits as opposed to 8-bit words. The serialised data for both buffers is then plotted onto a single plot. While the data is in serial format, we can truncate any non-valid bits such as in the case of DOCSIS Medium."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "tx_bits = hf.serialise(tx_enc_buf)\n",
    "rx_bits = hf.serialise(rx_enc_buf)\n",
    "\n",
    "# Truncate any bits which are not valid\n",
    "n = fec._code_params.ldpc[code_name]['n']\n",
    "rx_bits = rx_bits[0:n]\n",
    "\n",
    "hf.plot_samples('Tx and Rx',\n",
    "                [range(len(rx_bits)),range(len(tx_bits))],\n",
    "                [rx_bits,tx_bits])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the plot above, both buffers are overlaid. Blue shows the encoded data and red the information bits to be encoded. It can be observed that the first component of the plot, the information bits, match in both cases and only the red plot can be seen as it sits on top of the blue plot. The second component, the parity bits, only exist in the encoded (blue) data.\n",
    "\n",
    "Comparing the information bits in each buffer by plotting the difference between the buffers, it is shown that these match exactly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = fec._code_params.ldpc[code_name]['k']\n",
    "compare = abs(rx_bits[0:k] - tx_bits)\n",
    "hf.plot_samples('Compare Information Bits',\n",
    "                [range(len(compare))],\n",
    "                [compare])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The parity bits in the encoded data can be plotted separately for further inspection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parity = rx_bits[k:]\n",
    "hf.plot_samples('Parity Bits',\n",
    "                [range(len(parity))],\n",
    "                [parity])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Having encoded a block of data using one LDPC code, you can try encoding with other codes. Simply change the code above by altering the variable *code_name* in section 3.1 and create new buffers for moving the data.\n",
    "\n",
    "The final step is to store the encoded data so it can be used in the following notebook"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%store rx_bits tx_enc_buf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Conclusion <a class=\"anchor\" id=\"nb2_conclusion\"></a>\n",
    "That completes the notebook on encoding using SD-FEC on RFSoC. In this notebook we have learned how to inspect the LDPC parameters for an available code for a given SD-FEC instance by examining the Python dictionary that has been generated from the \\*.hwh file. Using these parameters and our knowledge of the programmable logic design, we were able to construct data buffers of the correct size for transmitting and receiving our data to and from the SD-FEC core. Additionally, we have learned how to construct and interpret the control and status words that are required to be written to and read from their respective interfaces on a block-by-block basis. Finally, we plotted the data buffers in serial form, allowing us to see that in LDPC encoding, information bits are not altered and an encoded block simply contains parity bits that have been appended to the original information bits. In the next notebook we will subject this encoded block to a channel, thereby introducing errors. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "[⬅️ Previous Notebook](01_fec_first_principles.ipynb) || [Next Notebook 🚀](03_fec_channel_simulation.ipynb)\n",
    "\n",
    "Copyright © 2023 Strathclyde Academic Media\n",
    "\n",
    "---\n",
    "---"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
